

namespace Stee.CAP8.MessageBus.Kafka.Configurations
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Topic configuration for Kafka message broker (equivalent of Confluent.Kafka.Admin.TopicSpecification)
    /// For more information about optional topic configurations, visit:
    /// https://docs.confluent.io/current/installation/configuration/topic-configs.html
    /// For dead-letter queues, increase "retention.ms" & "retention.bytes" as required.
    /// </summary>
    public sealed class TopicConfiguration : IEquatable<TopicConfiguration>
    {
        #region Constants

        /// <summary>
        /// Mandatory property for topic configuration: Name
        /// </summary>
        public const string TopicConfigName = "Name";

        /// <summary>
        /// Mandatory property for topic configuration: NumOfPartitions
        /// </summary>
        public const string TopicConfigNumOfPartitions = "NumOfPartitions";

        /// <summary>
        /// Mandatory property for topic configuration: ReplicationFactor
        /// </summary>
        public const string TopicConfigReplicationFactor = "ReplicationFactor";

        /// <summary>
        /// Property for topic configuration: Configs
        /// </summary>
        public const string TopicConfigOtherConfigs = "Configs";

        #endregion Constants


        /// <summary>
        /// Gets the topic name
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// Gets the number of topic partitions
        /// </summary>
        public int NumOfPartitions { get; }

        /// <summary>
        /// Gets the number of replicas for each partition
        /// </summary>
        public short ReplicationFactor { get; }

        /// <summary>
        /// Gets the configuration collection mappings
        /// </summary>
        private readonly Dictionary<string, string> configurations = new Dictionary<string, string>();


        #region Constructor

        /// <summary>
        /// Initializes a new instance of the TopicConfiguration class
        /// </summary>
        /// <param name="name">Topic name</param>
        /// <param name="numOfPartitions">Number of partitions in a topic. Recommended value is 4 and above.</param>
        /// <param name="replicaFactor">Number of replicas for each partition of a topic. Recommended value is 4 and above</param>
        /// <param name="configs"></param>
        public TopicConfiguration(string name, int numOfPartitions, short replicaFactor, IDictionary<string, string> configs)
        {
            name = name?.Trim();

            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException($"Null or empty message topic encountered: {nameof(name)}");
            }
            else if (numOfPartitions < 1)
            {
                throw new ArgumentException($"Topic partition count cannot be less than 1: {numOfPartitions}");
            }
            else if (replicaFactor < 1)
            {
                throw new ArgumentException($"Topic replication factor cannot be less than 1: {replicaFactor}");
            }

            this.Name = name;
            this.NumOfPartitions = numOfPartitions;
            this.ReplicationFactor = replicaFactor;

            if (configs?.Any() == true)
            {
                foreach (var item in configs)
                {
                    this.AddConfiguration(item.Key, item.Value);
                }
            }
        }

        #endregion Constructor


        #region Public Methods

        /// <summary>
        /// Add new configuration referenced by key.
        /// If key already exist, current value will be over-written.
        /// </summary>
        /// <param name="key">Identifier for configuration value</param>
        /// <param name="value">Configuration string value</param>
        /// <returns>Return TRUE if added else FALSE</returns>
        public bool AddConfiguration(string key, string value)
        {
            if (ValidateConfigKey(key))
            {
                this.configurations[key] = value;
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Remove existing configuration by key
        /// </summary>
        /// <param name="key">Identifier for configuration value</param>
        /// <returns>Return TRUE if removed else FALSE</returns>
        public bool RemoveConfiguration(string key)
        {
            return this.configurations.Remove(key);
        }

        /// <summary>
        /// Check for existing configuration key
        /// </summary>
        /// <param name="key">Identifier for configuration value</param>
        /// <returns>Return TRUE if </returns>
        public bool IsConfigurationExist(string key)
        {
            if (ValidateConfigKey(key))
            {
                return this.configurations.ContainsKey(key);
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Get the configuration by key identifier
        /// </summary>
        /// <param name="key">Key identifier</param>
        /// <returns>Return configuration string if found else NULL</returns>
        public string GetConfigurationByKey(string key)
        {
            return this.IsConfigurationExist(key) ? this.configurations[key] : null;
        }

        /// <summary>
        /// Gets the configuration mappings
        /// </summary>
        /// <returns>Return configuration mappings</returns>
        public Dictionary<string, string> GetAllConfigurations()
        {
            return new Dictionary<string, string>(this.configurations);
        }

        /// <summary>
        /// Gets all class property values and configuration mappings
        /// </summary>
        /// <returns>Return property values & configuration mappings</returns>
        public Dictionary<string, string> GetAllConfigurationsAndProperties()
        {
            var properties = new Dictionary<string, string>
            {
                { nameof(this.Name),                this.Name },
                { nameof(this.NumOfPartitions),     this.NumOfPartitions.ToString() },
                { nameof(this.ReplicationFactor),   this.ReplicationFactor.ToString() },
            };

            foreach (var item in this.configurations)
            {
                properties.Add(item.Key, item.Value);
            }

            return properties;
        }

        /// <summary>
        /// Compare equality method
        /// </summary>
        /// <param name="other">Other TopicConfiguration instance</param>
        /// <returns>Return TRUE if other instance is identical else FALSE</returns>
        public bool Equals(TopicConfiguration other)
        {
            return this.Equals((object)other);
        }

        #endregion Public Methods


        #region Overridden Methods

        /// <summary>
        /// Overridden Equals method
        /// </summary>
        /// <param name="obj">Other TopicConfiguration instance</param>
        /// <returns>Return TRUE if other instance is identical else FALSE</returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }
            else
            {
                return (obj is TopicConfiguration configuration) &&
                    string.Equals(this.Name, configuration.Name, StringComparison.InvariantCultureIgnoreCase);
            }
        }

        /// <summary>
        /// Overridden GetHashCode method
        /// </summary>
        /// <returns>Return the hash code</returns>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode(StringComparison.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// Overridden ToString method
        /// </summary>
        /// <returns>Return the stringified object</returns>
        public override string ToString()
        {
            return $"Topic: {this.Name}, Partition count: {this.NumOfPartitions}, Replication factor: {this.ReplicationFactor}";
        }

        #endregion Overridden Methods


        #region Static Methods

        /// <summary>
        /// Validate key name against class property names
        /// </summary>
        /// <param name="key">Header map key</param>
        /// <returns>Return TRUE if key does not match any class property names else FALSE</returns>
        private static bool ValidateConfigKey(string key)
        {
            var k = key?.Trim();

            if (string.IsNullOrEmpty(k))
            {
                throw new ArgumentNullException(nameof(key));
            }
            else
            {
                return !(string.Equals(k, nameof(Name), StringComparison.InvariantCultureIgnoreCase) ||
                    string.Equals(k, nameof(NumOfPartitions), StringComparison.InvariantCultureIgnoreCase) ||
                    string.Equals(k, nameof(ReplicationFactor), StringComparison.InvariantCultureIgnoreCase));
            }
        }

        #endregion Static Methods
    }
}
